/**
 * Copyright 2021 Inc.
 **/
package com.myz.rabbitmq.common.config;

import com.myz.rabbitmq.common.retry.RetryCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * @author maoyz0621 on 2021/6/10
 * @version v1.0
 */
public class RabbitMqConfirmAndReturn implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private static final Logger log = LoggerFactory.getLogger(RabbitMqConfirmAndReturn.class);

    /**
     * confirm机制只保证消息到达exchange，不保证消息可以路由到正确的queue,如果exchange错误，就会触发confirm机制
     *
     * @param correlationData 回调的相关数据.
     * @param ack             true for ack, false for nack
     * @param cause           专门给NACK准备的一个可选的原因，其他情况为null。
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.debug("Exchange接收是否成功（ack）: {};返回的用户参数（correlationData）:{};NACK原因: {}", ack, correlationData, cause);
        RetryCache retryCache = new RetryCache();
        if (ack) {
            log.info("消息发送到Exchange成功");
            // 删除 msgId 与 Message 的关系
            retryCache.del(correlationData.getId());
            // todo 业务逻辑
        } else {
            log.error("消息发送到Exchange失败:{}", cause);
            // todo 消息发送到exchange失败 ， 重新发送
        }
    }

    /**
     * 关于 SendConfirmCallBack 和 SendReturnCallback 的回调说明：
     * 1.如果消息没有到exchange,则confirm回调,ack=false
     * 2.如果消息到达exchange,则confirm回调,ack=true
     * 3.exchange到queue成功,则不回调return
     * 4.exchange到queue失败,则回调return(需设置mandatory=true,否则不回调,消息就丢了)
     */

    /**
     * 消息确认机制
     * Confirms给客户端一种轻量级的方式，能够跟踪哪些消息被broker处理，
     * 哪些可能因为broker宕掉或者网络失败的情况而重新发布。
     * 确认并且保证消息被送达，提供了两种方式：发布确认和事务。(两者不可同时使用)
     * 在channel为事务时，不可引入确认模式；同样channel为确认模式下，不可使用事务。
     */
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * Return 消息机制用于处理一个不可路由的消息。在某些情况下，如果我们在发送消息的时候，当前的 exchange 不存在或者指定路由 key 路由不到，这个时候我们需要监听这种不可达的消息
     * 就需要这种return机制
     * <p>
     * 实现此方法在basicPublish失败时回调
     * 相当于 ReturnListener的功能。
     * 在发布消息时设置mandatory等于true，
     * 监听消息是否有相匹配的队列，没有时将执行returnedMessage方法，消息将返给发送者
     * <p>
     * 使用return-callback时必须设置mandatory为true，或者在配置中设置mandatory-expression的值为true，
     * 可针对每次请求的消息去确定'mandatory'的boolean值，
     * 只能在提供'return-callback'时使用，与mandatory互斥
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        try {
            log.error("MsgSendReturnCallback [消息从交换机到队列失败] {};错误代码（replyCode）：{};消息对象:{}：routingKey:{}",
                    replyText, replyCode, new String(message.getBody(), StandardCharsets.UTF_8), routingKey);
            TimeUnit.MICROSECONDS.sleep(200);
            // 发送信息
            // rabbitTemplate.send(message);
        } catch (Exception e) {

        }
    }
}
